package com.sec.etech.bpm.cust.service;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.sec.etech.bpm.constant.DBConstant;
import com.sec.etech.org.service.EtechUserUtil;
import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.apache.commons.lang3.StringUtils;
import org.openbpm.base.core.util.AppUtil;
import org.openbpm.base.core.util.ExceptionUtil;
import org.openbpm.base.core.util.StringUtil;
import org.openbpm.bpm.api.engine.action.cmd.TaskActionCmd;
import org.openbpm.bpm.api.engine.data.BpmFlowDataAccessor;
import org.openbpm.bpm.api.engine.data.result.BpmFlowData;
import org.openbpm.bpm.core.model.BpmTask;
import org.openbpm.bus.api.model.IBusinessData;
import org.openbpm.form.api.model.FormType;
import org.openbpm.org.api.model.IUser;
import org.openbpm.sys.api.service.ISysDataSourceService;
import org.openbpm.sys.util.ContextUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

@Slf4j
@Service
public class EtechBpmHelper {

    @Autowired
    ISysDataSourceService sysDataSourceService;

    // 流程线程池，用于批量提交，自动提交节点
    private final ExecutorService executorService = new ThreadPoolExecutor(50, 200, 1, TimeUnit.SECONDS, new LinkedBlockingDeque());

    /**
     * 节点批量处理
     *
     * @param flowParams
     * @return
     */
    public JSONArray doActions(List<JSONObject> flowParams) {
        JSONArray results = new JSONArray();
        IUser curUser = ContextUtil.getCurrentUser();
        List<Future<JSONObject>> futures = new ArrayList<>();
        //ExecutorService executorService = ThreadUtil.newExecutor(flowParams.size());
        flowParams.forEach((jsonObject) -> {
            Future<JSONObject> future = executorService.submit(new Callable<JSONObject>() {
                public JSONObject call() throws Exception {
                    JSONObject result;
                    try {
                        ContextUtil.setCurrentUser(curUser);
                        result = getEtechBpmService().doAction(jsonObject);
                    } catch (Exception e) {
                        e.printStackTrace();
                        result = new JSONObject();
                        result.put("bizId", jsonObject.getString("bizId"));
                        result.put("flag", "0");
                        result.put("msg", ExceptionUtil.getRootErrorMseeage(e));
                    } finally {
                        ContextUtil.clearAll();
                    }
                    return result;
                }
            });
            futures.add(future);
        });
        futures.forEach((future) -> {
            try {
                results.add(future.get());
            } catch (Exception var4) {
                JSONObject result = new JSONObject();
                result.put("flag", "0");
                result.put("msg", ExceptionUtil.getRootErrorMseeage(var4));
                results.add(result);
            }

        });
        // executorService.shutdown();  //改为共用
        return results;
    }

    public boolean saveAutoSubmitTask(TaskActionCmd taskActionModel, String remark){
        BpmTask task = (BpmTask) taskActionModel.getBpmTask();
        String taskId = task.getTaskId();
        String action = "agree";
        String bizKey = null;
        Object bizId = null;
        Map<String, IBusinessData> bizDataMap = taskActionModel.getBizDataMap();
        if (bizDataMap != null && bizDataMap.size() > 0) {
            bizKey = bizDataMap.keySet().iterator().next();  //bizKey
            IBusinessData businessData = bizDataMap.get(bizKey);
            if (businessData != null) {
                bizId =   businessData.getPk();
            }
        }
        if(StringUtil.isEmpty(remark)){
            remark = "自动提交"+task.getNodeId()+"("+task.getDefId()+")";
        }
        return saveAutoSubmitTask(taskId, action, null, String.valueOf(bizId), null, remark);
    }

    /**
     * 提交自动完成任务（后台JOB）
     * @param taskId
     * @param userId
     * @param bizId
     * @return
     */
    public boolean saveAutoSubmitTask(String taskId, String userId, String bizId){
        return saveAutoSubmitTask(taskId, "agree", userId, bizId, null, null);
    }

    public boolean saveAutoSubmitTask(String taskId, String action, String userId, String bizId, Date submitTime, String remark){
        if(StringUtil.isEmpty(taskId)){
            return false;
        }
        String sql1 = "select * from bpm_task_autosubmit where task_id = ?";
        String sqlInsert = "insert into bpm_task_autosubmit(task_id,action,submit_userid,biz_id,submit_time,remark,create_time) values(?,?,?,?,?,?,now())";  //6
        String sqlUpdate = "update bpm_task_autosubmit set status=0, try_count=0, action=?,submit_userid=?, biz_id=?, submit_time=?, remark=?, last_update=now() where task_id=?"; //6

        JdbcTemplate jdbcTemplate = sysDataSourceService.getJdbcTemplateByKey(DBConstant.PLAN_DB);
        List<Map<String, Object>> list1 = jdbcTemplate.queryForList(sql1, taskId);
        if(list1!=null && !list1.isEmpty()){
            Map mTask = list1.get(0);
            if(StringUtil.isEmpty(action)){
                action = MapUtil.getStr(mTask, "action");
            }
            if(StringUtil.isEmpty(userId)){
                userId = MapUtil.getStr(mTask, "submit_userid");
            }
            if(StringUtil.isEmpty(bizId)){
                bizId = MapUtil.getStr(mTask, "biz_id");
            }
            if(ObjectUtil.isEmpty(submitTime)){
                submitTime = MapUtil.getDate(mTask, "submit_time");
            }
            String remark1 = MapUtil.getStr(mTask, "remark");
            if(StringUtil.isEmpty(remark)){
                remark = remark1;
            }else{
                remark += "\r\n"+remark1;
            }
            jdbcTemplate.update(sqlUpdate, action, userId, bizId, submitTime, remark, taskId);
        }else{
            jdbcTemplate.update(sqlInsert, taskId, action, userId, bizId, submitTime, remark);
        }
        return true;
    }


    // 每2分钟检查一次
    //@Scheduled(cron = "20 */2 * * * ? ")
    //@SchedulerLock(name = "checkAutosubmitTask", lockAtMostFor="5M", lockAtLeastFor="5M")
    public Boolean checkAutosubmitTask(){
        JdbcTemplate jdbcTemplate = sysDataSourceService.getJdbcTemplateByKey(DBConstant.PLAN_DB);

        // 对比task表排除已完成的任务（增加创建时间超过5分钟，避免任务还未生成）
        String sqlCheckTask = "update bpm_task_autosubmit a set status=2 " +
                "where status=0 and a.create_time< DATE_ADD(NOW(),INTERVAL -5 MINUTE)  " +
                "and (a.lock_time is null or a.lock_time < DATE_ADD(now(), INTERVAL -10 MINUTE)) " +
                "and (try_count>=3 or not EXISTS( select t.task_id_ from bpm_task t where t.task_id_=a.task_id))";
        jdbcTemplate.execute(sqlCheckTask);

        // 检查符合条件的任务列表
//        String sql = "select * from bpm_task_autosubmit " +
//                " where status=0 and try_count<3 and (submit_time is null or submit_time < now())";
//        select a.task_id, a.submit_userid, a.action,a.biz_id,t.inst_id_, t.def_id_, t.node_id_, t.assignee_id_
//                -- ,a.*, t.*
//                from bpm_task_autosubmit a, bpm_task t
//        where status=0 and try_count<3 and (submit_time is null or submit_time < now()) and a.task_id=t.task_id_;
        // 增加锁定时间条件， 同一个任务处理则增加锁定10分钟
        String sql = "select a.task_id, a.submit_userid, a.action,a.biz_id, t.inst_id_, t.def_id_, t.node_id_, t.assignee_id_ " +
                "from bpm_task_autosubmit a, bpm_task t " +
                "where status=0 and (lock_time is null or lock_time < DATE_ADD(now(), INTERVAL -10 MINUTE)) " +
                "and try_count<3 and (submit_time is null or submit_time < now()) and a.task_id=t.task_id_";
        String sqlBiz = "select biz_id_ from bpm_bus_link where inst_id_=?";


        List<Map<String, Object>> taskList = jdbcTemplate.queryForList(sql);
        log.info("checkAutosubmitTask taskList size="+taskList.size());
        List<Future<JSONObject>> futures = new ArrayList<>();
        for(Map<String, Object> mtask: taskList){

            final String taskId = MapUtil.getStr(mtask, "task_id");
            //更新锁定时间, 无需专门解锁
            updateAutosubmitTaskLock(taskId);

            // 线程池调用-提交自动任务

            Future<JSONObject> future = null;
            try{
                future = doAutosubmitTask(taskId, mtask);
            }catch(Exception e){
                e.printStackTrace();
                checkTaskResult(taskId, future, e);
            }

            // 两种方案： 1 同步处理  2 多线程异步
            if(future!=null){
                // 方案1：同步处理-等待结果
                checkTaskResult(taskId, future, null);
                // 方案2： 多线程异步
    //            futures.add(future);

            }
        }

        // 多线程检查结果
        futures.forEach((future) -> {
            checkTaskResult(future);
        });
        return true;
    }

    @Transactional(propagation= Propagation.REQUIRES_NEW)
    public Future<JSONObject> doAutosubmitTask(String taskId, Map<String, Object> mtask){
        Future<JSONObject> future = null;

        JSONObject flowParams = new JSONObject();
        flowParams.put("flagAutosubmitTask", "1");
        flowParams.put("taskId", taskId);
        String action = MapUtil.getStr(mtask, "action");
        if(StringUtil.isEmpty(action)){
            action = "agree";
        }
        flowParams.put("action", action);
        String actionUserId = MapUtil.getStr(mtask, "submit_userid");
        if(StringUtils.isEmpty(actionUserId)){
            String assigneeId = MapUtil.getStr(mtask, "assignee_id_");
            if(StringUtils.isNotEmpty(assigneeId) && !"0".equals(assigneeId)){
                actionUserId = assigneeId;
            }
        }
        if(StringUtil.isNotEmpty(actionUserId)){
            flowParams.put("actionUserId", actionUserId);
        }
        Long instId = MapUtil.getLong(mtask, "inst_id_");
        flowParams.put("instanceId", instId);
        String bizId = MapUtil.getStr(mtask, "biz_id");
        if(StringUtil.isEmpty(bizId)){
            // bpm_bus_loink可能有多个biz 仅用于备选方案
            JdbcTemplate jdbcTemplate = sysDataSourceService.getJdbcTemplateByKey(DBConstant.PLAN_DB);
            String sqlBiz = "select biz_id_ from bpm_bus_link where inst_id_=?";
            List<Map<String,Object>> list1 = jdbcTemplate.queryForList(sqlBiz, instId);
            if(list1!=null && !list1.isEmpty()){
                bizId = MapUtil.getStr(list1.get(0), "biz_id_");
            }
        }
        if(StringUtil.isNotEmpty(bizId)){
            flowParams.put("bizId", bizId);
            flowParams.put("businessKey", bizId);
        }
        JSONObject extendConf = new JSONObject();
        extendConf.put("nodeId", MapUtil.getStr(mtask, "node_id_"));
        flowParams.put("extendConf", extendConf);
        flowParams.put("defId", MapUtil.getLong(mtask, "def_id_"));

        future = executorService.submit(new Callable<JSONObject>() {
            public JSONObject call() throws Exception {
                JSONObject result = null;
                try {
                    IUser actionUser = null;
                    if(flowParams.get("actionUserId") != null){
                        actionUser = EtechUserUtil.getUserById(flowParams.getString("actionUserId"));
                    }
                    if(actionUser==null){
                        actionUser = EtechUserUtil.getUserAdmin();
                    }
                    ContextUtil.setCurrentUser(actionUser);

                    BpmFlowData bpmFlowData = getFlowTaskData(taskId);
                    flowParams.put("data", bpmFlowData.getData());
                    //flowParams.put("formKey", );

                    result = getEtechBpmService().doAction(flowParams);
                } catch (Exception e) {
                    e.printStackTrace();
                    result = new JSONObject();
                    result.put("bizId", flowParams.getString("bizId"));
                    result.put("flag", "0");
                    result.put("msg", ExceptionUtil.getRootErrorMseeage(e));
                } finally {
                    result.put("taskId", taskId);
                    ContextUtil.clearAll();
                }
                return result;
            }
        });
        return future;
    }

    protected void checkTaskResult(Future<JSONObject> future){
        this.checkTaskResult(null, future, null);
    }

    @Transactional(propagation= Propagation.REQUIRES_NEW)
    protected void checkTaskResult(String taskId, Future<JSONObject> future, Exception e){

        // 1. prepare
        JdbcTemplate jdbcTemplate = sysDataSourceService.getJdbcTemplateByKey(DBConstant.PLAN_DB);
        // 更新结果sql
        String sqlSuc = "update bpm_task_autosubmit set status=1, last_update=now(), last_msg=? where task_id=?";
        String sqlFail = "update bpm_task_autosubmit set try_count=try_count+1, last_update=now(), last_msg=? where task_id=?";

        // 2. record fail
        if(e!=null){
            jdbcTemplate.update(sqlFail, e.getMessage(), taskId);
            return;
        }

        // 3. get result
        try {
            JSONObject result = future.get();  //future.get(2, TimeUnit.MINUTES); // 设置2分钟超时
            taskId = result.getString("taskId");
            int flag = result.getIntValue("flag");
            String msg = result.getString("msg");
            if(flag==1){
                jdbcTemplate.update(sqlSuc, msg, taskId);
            }else{
                jdbcTemplate.update(sqlFail, msg, taskId);
            }
        } catch (Exception e2) {
            // 异常导致未获取结果
            jdbcTemplate.update(sqlFail, e2.getMessage(), taskId);
            e.printStackTrace();
        }
    }

    /**
     * 更新自动提交任务的锁定时间
     * 独立事务，及时提交
     * @param taskId
     * @return
     */
    @Transactional(propagation= Propagation.REQUIRES_NEW)
    public Boolean updateAutosubmitTaskLock(String taskId){
        String sqlLocktime = "update bpm_task_autosubmit set lock_time=now() where task_id=?";
        JdbcTemplate jdbcTemplate = sysDataSourceService.getJdbcTemplateByKey(DBConstant.PLAN_DB);
        jdbcTemplate.update(sqlLocktime, taskId);
        return true;
    }

    public BpmFlowData getFlowTaskData(String taskId){
        BpmFlowData bpmFlowData = ((BpmFlowDataAccessor)AppUtil.getBean(BpmFlowDataAccessor.class)).getFlowTaskData(taskId, FormType.fromValue(FormType.PC.value()));
        return bpmFlowData;
    }

    static ThreadLocal<Map<String, String>> autoSubmitTask = new ThreadLocal<>();//节点自动提交
    static ThreadLocal<Map<String,JSONObject>> startNewBpmInst = new ThreadLocal<>();//启动另一个流程

    public Map<String, String> getAutoSubmitTask(){
        return autoSubmitTask.get();
    }
    public Map<String,JSONObject> getStartNewBpmInst(){
        return startNewBpmInst.get();
    }

    public String setAutoSubmitTaskInThread(String taskId) throws Exception {
        Map<String, String> taskData = autoSubmitTask.get();
        if(taskData==null) {
            taskData = new ConcurrentHashMap();
        }
        taskData.put(taskId, taskId);
        autoSubmitTask.set(taskData);
        return null;
    }

    public String setStartNewBpmInstInThread(JSONObject object) throws Exception {
        String key = object.getString("bizId") + "," + object.getString("defId");
        Map<String, JSONObject> dataMap = startNewBpmInst.get();
        if(dataMap==null){
            dataMap = new ConcurrentHashMap();
        }
        dataMap.put(key, object);
        startNewBpmInst.set(dataMap);
        return null;
    }
    public void removeAutoSubmitTaskInThread(String taskId){
        Map taskData = autoSubmitTask.get();
        if(taskData!=null) {
            taskData.remove(taskId);
        }
    }
    public void removeStartNewBpmInstInThread(String bizId){
        Map taskData = startNewBpmInst.get();
        if(taskData!=null) {
            taskData.remove(bizId);
        }
    }
    //执行callBack方法
    public Object callBack(JSONObject object) throws Exception{
        Class<?> userClass = Class.forName(object.getString("classFullPath"));
        Method method = userClass.getMethod(object.getString("methodName"), JSONObject.class);
        Object resObj = method.invoke(userClass.newInstance(),object.getJSONObject("params"));
        return resObj;
    }



    private EtechBpmService _bpmService;
    private EtechBpmService getEtechBpmService(){
        if(_bpmService==null){
            _bpmService = (EtechBpmService)AppUtil.getBean("etechBpmService");
        }
        return _bpmService;
    }

    /////  从EtechBpmService迁移方法



    ///// 从EtechBpmService迁移方法 - 备用
    /**
     * 检查待处理流程的某个节点，如果存在，提交这个节点
     *
     * @param flowParams
     * @return
     */
    private JSONArray autoSubmitNode(List<JSONObject> flowParams) {
        List<JSONObject> params = new ArrayList<>();
        JdbcTemplate jdbcTemplate = sysDataSourceService.getJdbcTemplateByKey(DBConstant.PLAN_DB);
        for (JSONObject obj : flowParams) {
            //如果当前有nodeKey节点，提交这个节点
            if (obj.getLong("bpminstanceid") != null && obj.getLong("templateid") != null) {
                String sql = "SELECT t.flowDefKey,t.flowformkey FROM proj_plantask_template t WHERE t.id=?";
                List<Map<String, Object>> dataList = jdbcTemplate.queryForList(sql, obj.getLong("templateid"));
                List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT * FROM bpm_task WHERE node_id_=? AND inst_id_=?", obj.getString("nodeKey"), obj.getLong("bpminstanceid"));
                if (list != null && list.size() > 0) {
                    JSONObject jsonObject = new JSONObject();
                    JSONObject extendConf = new JSONObject();
                    extendConf.put("nodeId", list.get(0).get("node_id_"));
                    jsonObject.put("extendConf", extendConf);
                    jsonObject.put("taskId", list.get(0).get("task_id_"));
                    jsonObject.put("instanceId", obj.get("bpminstanceid"));
                    jsonObject.put("formKey", dataList.get(0).get("flowformkey"));
                    jsonObject.put("defKey", dataList.get(0).get("flowDefKey"));
                    jsonObject.put("bizId", obj.get("bizId"));
                    jsonObject.put("action", "agree");
                    params.add(jsonObject);
                }
            }
        }
        return this.doActions(params);
    }
}
